﻿using ArtMath.Core.Data;
using ArtMath.Core.Helper;
using ArtMath.Parameter;
using ArtMath.Resource;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using ArtMath.CommonType;

namespace ArtMath.Data
{
    /// <summary>
    /// 适用于文档的带值表达式
    /// </summary>
    [Serializable]
    public class DocStringX : StringX
    {
        protected HashSet<BasicParameter> relatedParameters = new HashSet<BasicParameter>();
        public event Action<DocStringX> ParametersValueChanging;
        public event Action<DocStringX, ParametersValueChangedEventArgs> ParametersValueChanged;
        [Browsable(false)]
        public Document LinkedDocument { get; }
        public DocStringX(Document doc)
        {
            LinkedDocument = doc;
        }
        public DocStringX(Document doc, string expression) : this(doc)
        {
            SetExpression(expression, false);
        }
        public DocStringX(Document doc, double value) : this(doc)
        {
            SetExpression(value, false);
        }
        public override string DirectExpression
        {
            get
            {
                return base.DirectExpression;
            }

            set
            {
                base.DirectExpression = value;
                foreach (BasicParameter item in relatedParameters)
                {
                    unbindParameter(item);
                }
                relatedParameters.Clear();
                foreach (BasicParameter item in LinkedDocument.Parameters)
                {
                    if (item.ObjectState == ObjectState.Normal)
                    {
                        if (StandardizedExpression.Contains(item.Name))
                        {
                            bindParameter(item);
                        }
                    }
                }
            }
        }
        [Browsable(false)]
        public bool ContainsParameter
        {
            get { return relatedParameters.Count > 0; }
        }
        protected override void setDouble()
        {
            foreach (BasicParameter item in relatedParameters)
            {
                unbindParameter(item);
            }
            relatedParameters.Clear();
        }
        protected override bool setString(string origin, string std, bool showMsg)
        {
            BasicParameter[] oldPara_bkp = new BasicParameter[relatedParameters.Count];
            int i = 0;
            foreach (BasicParameter item in relatedParameters)
            {
                oldPara_bkp[i++] = item;
                unbindParameter(item);
            }
            relatedParameters.Clear();
            foreach (BasicParameter item in LinkedDocument.Parameters)
            {
                if (item.ObjectState == ObjectState.Normal)
                {
                    if (StandardizedExpression.Contains(item.Name))
                    {
                        std = Regex.Replace(std, $"\\b{item.Name}\\b", $"({item.Value.Value})");
                        //std = std.Replace(item.Name, item.Value.Value.ToString());
                        bindParameter(item);
                    }
                }
            }
            if (base.setString(origin, std, showMsg))
            {
                return true;
            }
            else
            {
                foreach (BasicParameter item in relatedParameters)
                {
                    unbindParameter(item);
                }
                relatedParameters.Clear();
                foreach (BasicParameter item in oldPara_bkp)
                {
                    bindParameter(item);
                }
                return false;
            }
        }
        protected virtual void valueChanged(BasicParameter sender, ValueChangedEventArgs e)
        {
            ParametersValueChanging?.Invoke(this);
            ParametersValueChangedEventArgs args = new ParametersValueChangedEventArgs(Value, sender);
            if (CanCalc)
            {
                string std = StandardizedExpression;
                foreach (BasicParameter item in relatedParameters)
                {
                    std = Regex.Replace(std, $"\\b{item.Name}\\b", $"({item.Value.Value})");
                    //std = std.Replace(item.Name, item.Value.Value.ToString());
                }
                if (!Calculation.Calculate(std, out val))
                {
                    return;
                }
            }
            ParametersValueChanged?.Invoke(this, args);
        }
        void bindParameter(BasicParameter p)
        {
            if (relatedParameters.Add(p))
            {
                p.ValueChanged += valueChanged;
                p.ReferenceCount++;
            }
        }
        void unbindParameter(BasicParameter p)
        {
            if (relatedParameters.Contains(p))
            {
                p.ValueChanged -= valueChanged;
                p.ReferenceCount--;
                //移除操作不能在遍历时执行
            }
        }
        public void ForEachRelatedParameter(Predicate<BasicParameter> func)
        {
            if (func == null) return;
            foreach (BasicParameter item in relatedParameters)
            {
                if (!func(item))
                {
                    break;
                }
            }
        }
        /// <summary>
        /// 解除引用所有参数
        /// </summary>
        public void UnbindAllParameters()
        {
            foreach (BasicParameter item in relatedParameters)
            {
                unbindParameter(item);
            }
        }
        /// <summary>
        /// 在含有DocStringX的对象从回收站恢复前对关联的参数进行检查
        /// </summary>
        /// <param name="desc">此对象的描述</param>
        public void SwitchDeletedParameters(string desc)
        {
            Dictionary<BasicParameter, BasicParameter> replace = null;
            foreach (BasicParameter item in relatedParameters)
            {
                //参数正常，引用+1
                if (item.ObjectState == ObjectState.Normal)
                {
                    item.ReferenceCount++;
                    continue;
                }
                //参数已被删除，尝试恢复
                if (item.CanRecover)
                {
                    try
                    {
                        item.Recover();
                        continue;
                    }
                    catch (Exception)
                    {

                    }
                }
                //从正常参数中查找同名参数
                BasicParameter newP = LinkedDocument.Parameters.Find((BasicParameter p) =>
                {
                    return !ReferenceEquals(p, item) && p.ObjectState == ObjectState.Normal && p.Name == item.Name;
                });
                if (newP == null)
                {
                    //同名参数未找到，尝试从回收站中恢复
                    DialogResult res = MessageBox.Show(string.Format(Resources.Translate("ParamNotExist"), desc, item.Name) + Resources.FullStop + string.Format(Resources.Translate("TryRecoverParam"), item.Name), Resources.Translate("Ask"), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                    switch (res)
                    {
                        case DialogResult.Cancel:
                            throw new OperationCanceledException();
                        case DialogResult.Yes:
                            bool success = false;
                            foreach (BasicParameter param in LinkedDocument.Parameters)
                            {
                                if (param.CanRecover && param.Name == item.Name)
                                {
                                    try
                                    {
                                        param.Recover();
                                        success = true;
                                        break;
                                    }
                                    catch (Exception)
                                    {

                                    }
                                }
                            }
                            if (!success)
                            {
                                throw new ObjectDisposedException(item.Name);
                            }
                            break;
                        case DialogResult.No:
                            throw new ObjectDisposedException(item.Name, string.Format(Resources.Translate("ParamNotExist"), desc, item.Name));
                    }
                }
                else
                {
                    if (replace == null)
                    {
                        replace = new Dictionary<BasicParameter, BasicParameter>();
                    }
                    replace.Add(item, newP);
                }
            }
            if (replace != null)
            {
                foreach (KeyValuePair<BasicParameter, BasicParameter> item in replace)
                {
                    unbindParameter(item.Key);
                    relatedParameters.Remove(item.Key);
                    bindParameter(item.Value);
                }
                SetExpression(DirectExpression, true, false);
            }
        }
    }
    public class ParametersValueChangedEventArgs : EventArgs
    {
        public double OldValue { get; }
        public BasicParameter ChangedParameter { get; }
        public ParametersValueChangedEventArgs(double oldValue, BasicParameter changedParam)
        {
            OldValue = oldValue;
            ChangedParameter = changedParam;
        }
    }
}
